﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Xml;
#if L2S
using System.Data.Linq;
using System.Data.Linq.Mapping;
#endif

namespace ALinq.Mapping
{
    ///<summary>
    /// Helper to build mapping between data-context and database.
    ///</summary>
    public static class FluentMappingHelper
    {
        /// <summary>
        /// Create an assocation mapping between a member and a constraint;
        /// </summary>
        /// <param name="typeMapping">The domain object type mapping.</param>
        /// <param name="member">Member mapping to the constraint.</param>
        /// <param name="thisKey">A list of members representing the values on this side of the association.</param>
        /// <param name="otherKey">A list of members that represents the values on the other side of the association.</param>
        /// <typeparam name="TEntity">The type on the this side of the association.</typeparam>
        /// <typeparam name="TOther">The type on the other side of the association.</typeparam>
        /// <typeparam name="T1">Type of this key.</typeparam>
        /// <typeparam name="T2">Type of other key.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Association<TEntity, TOther, T1, T2>(this TypeMapping<TEntity> typeMapping,
                                                                 Expression<Func<TEntity, IEnumerable<TOther>>> member,
                                                                 Expression<Func<TEntity, T1>> thisKey,
                                                                 Expression<Func<TOther, T2>> otherKey)
            where TEntity : class
            where TOther : class
        {
            return Association(typeMapping, member, thisKey, otherKey, null);
        }

        /// <summary>
        /// Create an assocation mapping between a member and a constraint;
        /// </summary>
        /// <param name="typeMapping">The domain object type mapping.</param>
        /// <param name="member">Member mapping to the constraint.</param>
        /// <param name="thisKey">A list of members representing the values on this side of the association.</param>
        /// <param name="otherKey">A list of members that represents the values on the other side of the association.</param>
        /// <param name="func">Callback function to assign values for the association-mapping.</param>
        /// <typeparam name="TEntity">The type on the this side of the association.</typeparam>
        /// <typeparam name="TOther">The type on the other side of the association.</typeparam>
        /// <typeparam name="T1">Type of this key.</typeparam>
        /// <typeparam name="T2">Type of other key.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Association<TEntity, TOther, T1, T2>(this TypeMapping<TEntity> typeMapping,
                                                                 Expression<Func<TEntity, IEnumerable<TOther>>> member,
                                                                 Expression<Func<TEntity, T1>> thisKey,
                                                                 Expression<Func<TOther, T2>> otherKey,
                                                                 Action<AssociationMapping> func)
            where TEntity : class
            where TOther : class
        {
            return CreateAssociation(typeMapping, member, thisKey, otherKey, func);
        }

        /// <summary>
        /// Create an assocation mapping between a member and a constraint;
        /// </summary>
        /// <param name="typeMapping">The domain object type mapping.</param>
        /// <param name="member">Member mapping to the constraint.</param>
        /// <param name="thisKey">A list of members representing the values on this side of the association.</param>
        /// <param name="otherKey">A list of members that represents the values on the other side of the association.</param>
        /// <typeparam name="TEntity">The type on the this side of the association.</typeparam>
        /// <typeparam name="TOther">The type on the other side of the association.</typeparam>
        /// <typeparam name="T1">Type of this key.</typeparam>
        /// <typeparam name="T2">Type of other key.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Association<TEntity, TOther, T1, T2>(this TypeMapping<TEntity> typeMapping,
                                                               Expression<Func<TEntity, TOther>> member,
                                                               Expression<Func<TEntity, T1>> thisKey,
                                                               Expression<Func<TOther, T2>> otherKey)
            where TEntity : class
            where TOther : class
        {
            return Association(typeMapping, member, thisKey, otherKey, null);
        }

        /// <summary>
        /// Create an assocation mapping between a member and a constraint;
        /// </summary>
        /// <param name="typeMapping">The domain object type mapping.</param>
        /// <param name="member">Member mapping to the constraint.</param>
        /// <param name="thisKey">A list of members representing the values on this side of the association.</param>
        /// <param name="otherKey">A list of members that represents the values on the other side of the association.</param>
        /// <param name="func">Callback function to assign values for the association-mapping.</param>
        /// <typeparam name="TEntity">The type on the this side of the association.</typeparam>
        /// <typeparam name="TOther">The type on the other side of the association.</typeparam>
        /// <typeparam name="T1">Type of this key.</typeparam>
        /// <typeparam name="T2">Type of other key.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Association<TEntity, TOther, T1, T2>(this TypeMapping<TEntity> typeMapping,
                                                               Expression<Func<TEntity, TOther>> member,
                                                               Expression<Func<TEntity, T1>> thisKey,
                                                               Expression<Func<TOther, T2>> otherKey,
                                                               Action<AssociationMapping> func)
            where TEntity : class
            where TOther : class
        {
            return CreateAssociation(typeMapping, member, thisKey, otherKey, func);
        }

        static TypeMapping<TEntity> CreateAssociation<TEntity>(this TypeMapping<TEntity> typeMapping,
                                                                 LambdaExpression member,
                                                                 LambdaExpression thisKey,
                                                                 LambdaExpression otherKey,
                                                                 Action<AssociationMapping> func)
        {
            var memberInfo = ((MemberExpression)member.Body).Member;
            MemberInfo[] thisMembers;
            MemberInfo[] otherMembers;

            var nodeType = thisKey.Body.NodeType;
            if (nodeType != ExpressionType.New && nodeType != ExpressionType.MemberAccess)
                throw Error.NotExpectedBodyType2("thisKey", nodeType, ExpressionType.New, ExpressionType.MemberAccess);

            nodeType = otherKey.Body.NodeType;
            if (nodeType != ExpressionType.New && nodeType != ExpressionType.MemberAccess)
                throw Error.NotExpectedBodyType2("otherKey", nodeType, ExpressionType.New, ExpressionType.MemberAccess);


            if (thisKey.Body.NodeType == ExpressionType.New)
                thisMembers = ((NewExpression)thisKey.Body).Arguments.Select(o => ((MemberExpression)o).Member).ToArray();
            else
                thisMembers = new[] { ((MemberExpression)thisKey.Body).Member };

            if (otherKey.Body.NodeType == ExpressionType.New)
                otherMembers = ((NewExpression)otherKey.Body).Arguments.Select(o => ((MemberExpression)o).Member).ToArray();
            else
                otherMembers = new[] { ((MemberExpression)otherKey.Body).Member };

            AssociationMapping assocMapping = typeMapping.GetAssociation(memberInfo);
            if (assocMapping == null)
                assocMapping = ModelMappingFactory.CreateAssociationAttribute(memberInfo);

            assocMapping.ThisMember = thisMembers;
            assocMapping.OtherMember = otherMembers;

            var type1 = thisMembers[0].DeclaringType;
            var type2 = otherMembers[0].DeclaringType;

            assocMapping.Name = string.Format("{0}_{1}", type1.Name, type2.Name);

            typeMapping.Associations.Add(assocMapping);
            if (func != null)
                func(assocMapping);

            return typeMapping;
        }

        /// <summary>
        /// Create a column-mapping between a member of TEntity type and a column in the database table or view;
        /// </summary>
        ///<param name="typeMapping">The domain object type mapping.</param>
        ///<param name="member">Member of the TEntity type.</param>
        ///<typeparam name="TEntity">The domain object type.</typeparam>
        ///<typeparam name="T">Type of the member.</typeparam>
        ///<returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Column<TEntity, T>(this TypeMapping<TEntity> typeMapping,
                                                             Expression<Func<TEntity, T>> member)
        {
            return Column(typeMapping, member, null);
        }

        /// <summary>
        /// Create a column-mapping between a member of TEntity type and a column in the database table or view;
        /// </summary>
        ///<param name="typeMapping">The domain object type mapping.</param>
        ///<param name="member">Member of the TEntity type.</param>
        ///<param name="func">Callback function to assign values for the column-mapping.</param>
        ///<typeparam name="TEntity">The domain object type.</typeparam>
        ///<typeparam name="T">Type of the member.</typeparam>
        ///<returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Column<TEntity, T>(this TypeMapping<TEntity> typeMapping,
                                                             Expression<Func<TEntity, T>> member, Action<ColumnMapping> func)
        {
            Error.ValidateArgumentNotNull(() => typeMapping);
            Error.ValidateArgumentNotNull(() => member);
            Error.ValidateIsMemberExpression(() => member);

            var memberInfo = ((MemberExpression)member.Body).Member;
            var colMapping = typeMapping.GetColumn(memberInfo);
            if (colMapping == null)
            {
                colMapping = ModelMappingFactory.CreateColumnMapping(memberInfo);
                typeMapping.Columns.Add(colMapping);
            }

            if (func != null)
                func(colMapping);

            return typeMapping;
        }

        /// <summary>
        /// Create a column-mapping between a member of the TEntity type and a primary column in the database table or view;
        /// </summary>
        /// <param name="typeMapping">The domain object type mapping.</param>
        /// <param name="member">Member of the TEntity type.</param>
        /// <param name="func">Callback function to assign values for the column-mapping.</param>
        /// <typeparam name="TEntity">The domain object type.</typeparam>
        /// <typeparam name="T">Type of the member.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> PrimaryKey<TEntity, T>(this TypeMapping<TEntity> typeMapping,
                                                         Expression<Func<TEntity, T>> member, Action<ColumnMapping> func = null) where TEntity : class
        {
            Error.ValidateArgumentNotNull(() => typeMapping);
            Error.ValidateArgumentNotNull(() => member);
            Error.ValidateIsMemberExpression(() => member);

            Column(typeMapping, member, columnMapping =>
            {
                if (func != null)
                    func(columnMapping);

                columnMapping.IsPrimaryKey = true;
                columnMapping.CanBeNull = false;

            });
            return typeMapping;
        }

        //=========================================== Table Begin ===========================================
        /// <summary>
        /// Create a table-mapping between a domain object type and a table or view in the database;
        /// </summary>
        /// <param name="databaseMapping">The database-mapping.</param>
        /// <param name="member">Member of the data-context.</param>
        /// <typeparam name="TDataContext">Type of the data-context.</typeparam>
        /// <typeparam name="TEntity">The domain object type.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Table<TDataContext, TEntity>(this DatabaseMapping<TDataContext> databaseMapping,
                                                             Expression<Func<TDataContext, ITable<TEntity>>> member) where TEntity : class
        {
            var typeMapping = Table(databaseMapping, member, null, null);
            return typeMapping;
        }

        /// <summary>
        /// Create a table-mapping between a domain object type and a table or view in the database;
        /// </summary>
        /// <param name="databaseMapping">The database-mapping.</param>
        /// <param name="member">Member of the data-context.</param>
        /// <param name="tableName">Name of the table or view in the database.</param>
        /// <typeparam name="TDataContext">Type of the data-context.</typeparam>
        /// <typeparam name="TEntity">The domain object type.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Table<TDataContext, TEntity>(this DatabaseMapping<TDataContext> databaseMapping,
                                                            Expression<Func<TDataContext, ITable<TEntity>>> member,
                                                            string tableName) where TEntity : class
        {
            var typeMapping = Table(databaseMapping, member, tableName, null);
            return typeMapping;
        }

        /// <summary>
        /// Create a table-mapping between a domain object type and a table or view in the database;
        /// </summary>
        /// <param name="databaseMapping">The database-mapping.</param>
        /// <param name="member">Member of the data-context.</param>
        /// <param name="func">Callback function to assign values for the type-mapping.</param>
        /// <typeparam name="TDataContext">Type of the data-context.</typeparam>
        /// <typeparam name="TEntity">The domain object type.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Table<TDataContext, TEntity>(this DatabaseMapping<TDataContext> databaseMapping,
                                                        Expression<Func<TDataContext, ITable<TEntity>>> member,
                                                        Action<TypeMapping<TEntity>> func) where TEntity : class
        {
            var typeMapping = Table(databaseMapping, member, null, func);
            return typeMapping;
        }

        /// <summary>
        /// Create a table-mapping between a domain object type and a table or view in the database;
        /// </summary>
        /// <param name="databaseMapping">The database-mapping.</param>
        /// <param name="member">Member of the data-context.</param>
        /// <param name="tableName">Name of the table or view in the database.</param>
        /// <param name="func">Callback function to assign values for the type-mapping.</param>
        /// <typeparam name="TDataContext">Type of the data-context.</typeparam>
        /// <typeparam name="TEntity">The domain object type.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Table<TDataContext, TEntity>(this DatabaseMapping<TDataContext> databaseMapping,
                                                                  Expression<Func<TDataContext, ITable<TEntity>>> member,
                                                                  string tableName, Action<TypeMapping<TEntity>> func) where TEntity : class
        {
            TableMapping<TEntity> tableMapping = (TableMapping<TEntity>)databaseMapping.GetTable(typeof(TEntity));
            if (tableMapping == null)
            {
                tableMapping = ModelMappingFactory.CreateTableMapping<TEntity>();
                databaseMapping.Tables.Add(tableMapping);
            }

            tableMapping.Member = ((MemberExpression)member.Body).Member;
            if (tableName == null)
                tableName = typeof(TEntity).Name;

            tableMapping.Name = tableName;

            if (func != null)
                func(tableMapping.RowType);

            return tableMapping.RowType;
        }
        //=========================================== Table End ===========================================

        //=========================================== Inheritance Begin ===========================================
        /// <summary>
        /// Create a inheritance type-mapping for the type-mapiing.
        /// </summary>
        /// <param name="typeMapping">The domain object type mapping.</param>
        /// <typeparam name="TEntity">The domain object type.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        public static TypeMapping<TEntity> Inheritance<TEntity>(this TypeMapping typeMapping) where TEntity : class
        {
            return Inheritance<TEntity>(typeMapping, null);
        }

        /// <summary>
        /// Create a inheritance type-mapping for the type-mapiing.
        /// </summary>
        /// <param name="typeMapping">The domain object type mapping.</param>
        /// <param name="func">Callback function to assign values for the type-mapping.</param>
        /// <typeparam name="TEntity">The domain object type.</typeparam>
        /// <returns>The domain object type mapping.</returns>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// class NorthwindMappingSource : FluentMappingSource
        /// {
        ///     public NorthwindMappingSource(Action<DatabaseMapping> func = null)
        ///     {
        ///         this.Map<NorthwindDatabase>(mapping =>
        ///         {
        ///             mapping.ProviderType = typeof(AccessDbProvider);
        ///             mapping.Name = typeof(NorthwindDatabase).Name;
        ///             mapping.Table(o => o.Contacts, "Contacts", o => { o.InheritanceCode = "Unknow"; o.IsInheritanceDefault = true; })
        ///                    .PrimaryKey(o => o.ContactID, o => { o.Storage = "_ContactID"; })
        ///                    .Column(o => o.ContactType, o => o.IsDiscriminator = true)
        ///                    .Column(o => o.CompanyName)
        ///                    .Column(o => o.Phone)
        ///                    .Column(o => o.GUID)
        ///                    .Inheritance<FullContact>()
        ///                    .Column(o => o.ContactName)
        ///                    .Column(o => o.ContactTitle)
        ///                    .Column(o => o.Address)
        ///                    .Column(o => o.City)
        ///                    .Column(o => o.Region)
        ///                    .Column(o => o.PostalCode)
        ///                    .Column(o => o.Country)
        ///                    .Column(o => o.Fax)
        ///                    .Inheritance<SupplierContact>()
        ///                    .Column(o => o.HomePage)
        ///                    .Inheritance<EmployeeContact>()
        ///                    .Column(o => o.PhotoPath)
        ///                    .Column(o => o.Photo)
        ///                    .Column(o => o.Extension)
        ///                    .Inheritance<CustomerContact>()
        ///                    .Inheritance<ShipperContact>();
        ///
        ///             if (func != null)
        ///                 func(mapping);
        ///         });
        ///     }
        /// }
        /// ]]>
        /// </code>
        /// </example>
        public static TypeMapping<TEntity> Inheritance<TEntity>(this TypeMapping typeMapping, Action<TypeMapping<TEntity>> func) where TEntity : class
        {
            var baseType = typeof(TEntity).BaseType;
            if (baseType == null)
                throw Error.NotInheritanceType(typeof(TEntity));

            var p = typeMapping.Parent;
            while (p != null && p.MappingType != ModelMappingType.Table)
                p = p.Parent;

            //====================================================
            // 说明：遍历所有 Type
            TypeMapping baseTypeMapping = null;
            Debug.Assert(p != null);
            var stack = new Stack<TypeMapping>();
            stack.Push(((TableMapping)p).RowType);
            while (stack.Count > 0)
            {
                var t = stack.Pop();
                if (t.Type == baseType)
                {
                    baseTypeMapping = t;
                    break;
                }

                foreach (var c in t.DerivedTypes)
                    stack.Push(c);
            }
            //====================================================
            if (baseTypeMapping == null)
                throw Error.BaseTypeNotMappingType(typeof(TEntity), baseType);

            var subTypeMapping = new TypeMapping<TEntity>();
            baseTypeMapping.DerivedTypes.Add(subTypeMapping);

            if (func != null)
                func(subTypeMapping);

            //if (subTypeMapping.IsInheritanceDefault && subTypeMapping.InheritanceCode == null)
            //    subTypeMapping.InheritanceCode = typeof(TEntity).Name;

            return subTypeMapping;
        }
        //=========================================== Inheritance End ===========================================
    }
}
